#!/usr/bin/env bash
# Copyright (c) 2021 maminjie <canpool@163.com>
# SPDX-License-Identifier: MulanPSL-2.0

method_def spec

# spec_get_tagvalue tag file
# spec_get_tagvalue tag specdata
spec_get_tagvalue() {
    local tag="$1"
    local spec="$2"
    if [ -f "$spec" ]; then
        spec="$(cat "$spec")"
    fi
    echo "$spec" | grep "^$tag:" | head -n1 | awk '{print $2}'
}

# spec_get_changelog file
# spec_get_changelog specdata
spec_get_changelog() {
    local spec="$1"
    if [ -f "$spec" ]; then
        spec="$(cat "$spec")"
    fi
    echo "$spec" | awk '{
        if ($1 ~ /^%changelog/) {
            flag = 1
        } else if (flag == 1) {
            print $0
        }
    }'
}

__spec_get_first_changelog() {
    awk '{
        if ($1 ~ /^%changelog/) {
            flag = 1
        } else if (flag == 1) {
            if ($1 ~ /^*/) { print $0; exit }
        }
    }'
}

# spec_get_developer file
# spec_get_developer specdata
#   Get the infos of developer from changelog
# Returns:
#   names and emails
spec_get_developer() {
    local changelog=$(spec_get_changelog "$1")
    if [ -z "$changelog" ]; then
        return
    fi
    echo "$changelog" | grep -E "^\*" |\
        sed -r -e 's/.*[0-9]{4} (.*)(<.*>).*/\1\2/g' -e 's/ *(<)/ \1/g' | uniq
}

spec_get_developer_email() {
    local info=$(spec_get_developer "$1")
    if [ -z "$info" ]; then
        return
    fi
    echo "$info" | awk -F '<' '{print $2}' | sed 's/>$//g' | uniq
}

# Returns: 0 or !0
__spec_check_rpmspec() {
    type rpmspec &>/dev/null
}

__spec_rpmspec_parse() {
    local spec="$1"
    if [ -f "$spec" ]; then
        spec=$(rpmspec -P "$spec" 2>/dev/null)
    else
        echo "$spec" > .tmp1024.spec
        spec=$(rpmspec -P .tmp1024.spec 2>/dev/null)
        rm -f .tmp1024.spec
    fi
    echo "$spec"
}

__spec_simple_parse() {
    local spec="$1"
    if [ -f "$spec" ]; then
        spec="$(cat "$spec")"
    fi
    local macros=$(echo "$spec" | grep -E "%(define|global)" | grep -vE "\(|/|\\\\" |\
        sed -r 's/%|\{|\?|\}|!//g' | awk '{printf("%s/%s\n", $2, $3)}' | sort -u)
    local pattern="/[0-9]*\.*[a-zA-Z\-\.]+"
    local ms1=$(echo "$macros" | grep -E "$pattern")    # with alpha
    local ms2=$(echo "$macros" | grep -vE "$pattern")

    for m in $ms1 $ms2; do
        local o=$(echo "$m" | awk -F '/' '{print $1}')
        local n=$(echo "$m" | awk -F '/' '{print $2}')
        spec=$(echo "$spec" | sed -r -e "s/%*\{*\<$o\>\}*/$n/g")
    done
    spec=$(echo "$spec" | grep -vE "%(define|global)")
    # don't supports cmd parse, as: %(cmdxx), so try to get from changelog
    local changelog=$(echo "$spec" | __spec_get_first_changelog)
    local version=$(spec_get_tagvalue "Version" "$spec")
    if [[ "$version" =~ \(|% ]]; then
        if [ -n "$changelog" ]; then
            local tmp=$(echo "$changelog" | awk '{print $NF}' | sed -r 's/^-|-[\sa-zA-Z0-9\._]+$//g' |\
                grep -E "[0-9]" | sed -r 's/.*://g')
            if [ -n "$tmp" ]; then
                spec=$(echo "$spec" | sed "s/$version.*/$tmp/g")
                version="$tmp"
            fi
        fi
    fi
    local release=$(spec_get_tagvalue "Release" "$spec")
    if [[ "$release" =~ \(|% ]]; then
        if [ -n "$changelog" ]; then
            local tmp=$(echo "$changelog" | awk '{print $NF}' | grep -E "-" | awk -F '-' '{print $NF}' | grep -E "[0-9]")
            if [ -n "$tmp" ]; then
                spec=$(echo "$spec" | sed "s/$release.*/$tmp/g")
                release="$tmp"
            fi
        fi
    fi
    # remove invalid number, as: %{?dist} and %{?0.} and %{?0.""} and %{?.""}
    spec=$(echo "$spec" | sed -r -e "s/%\{*(v|V)ersion\}*/$version/g" -e "s/%\{*(r|R)elease\}*/$release/g" -e 's/%\{\?.*\}//g')

    local name=$(spec_get_tagvalue "Name" "$spec")
    spec=$(echo "$spec" | sed -r -e "s/%\{*(n|N)ame\}*/$name/g")

    local url=$(spec_get_tagvalue "URL" "$spec")
    url=$(echo "$url" | sed 's|\/|\\/|g')
    spec=$(echo "$spec" | sed -r -e "s/%\{*(u|U)rl\}*/$url/g")

    echo "$spec"
}

# spec_parse file
# spec_parse specdata
spec_parse() {
    local spec="$1"
    __spec_check_rpmspec
    if [ $? -eq 0 ]; then
        local data=$(__spec_rpmspec_parse "$spec")
        if [ -n "$data" ]; then
            echo "$data"; return
        fi
    fi
    __spec_simple_parse "$spec"
}

# spec_get_version file
# spec_get_version specdata
spec_get_version() {
    local spec="$(spec_parse "$1")"
    spec_get_tagvalue "Version" "$spec"
}

# spec_get_release file
# spec_get_release specdata
spec_get_release() {
    local spec="$(spec_parse "$1")"
    spec_get_tagvalue "Release" "$spec"
}

spec_align_tag() {
    awk '{
        if ($0 ~ /^[A-Z][[:alnum:]]*: /) {
            va = "";
            for (i = 2; i <= NF; i++) {
                va = va" "$i
            };
            printf("%-20s%s\n", $1, va);
        } else {
            print $0;
        }
    }'
}

spec_merge_tag() {
    awk -v key="$1" -v max_len=80 -F : '
    BEGIN {
        tmp = "";
    } {
        if (key == $1) {
            if (NF > 2) {   # value contains :
                val = sub(/[[:alnum:]]+:/, "", $0);
            } else {
                $1 =""; val = $0;
            }

            tmp1 = tmp val;
            gsub(" +", " ", tmp1);
            if (length(tmp1) > max_len) {
                gsub(" +", " ", tmp);
                printf("%-20s%s\n",key":", tmp);
                tmp = "";
            }
            tmp = tmp val;
        } else {
            if (length(tmp) != 0) {     # print remains
                gsub(" +", " ", tmp);
                printf("%-20s%s\n", key":", tmp);
                tmp = "";
            }
            print $0;
        }
    }'
}

# __spec_update file log reset
__spec_update() {
    local file="$1"
    local log="$2"
    local reset="$3"

    # get verison release
    local version=$(spec_get_version "$file")
    local epoch=$(spec_get_tagvalue "Epoch" "$file")
    local release_old=$(spec_get_release "$file")
    local release=$release_old
    if [ "X$reset" = "Xy" ]; then
        release=1
    else
        release=$(echo "$release" | sed 's/%{.*}//')
        release=${release%%.*}  # remove decimal
        release=$((release+1))
        local release_org=$(spec_get_tagvalue "Release" "$file")
        if [[ "$release_org" =~ % ]]; then
            release_org=$(echo "$release_org" | sed -r 's/%|\{|\?|\}|!//g') # only support single macro now
            sed -i -r "/^%(global|define)\s+${release_org}/s/${release_old}/${release}/" "$file"
        fi
    fi
    local version_str="$version-$release"
    if [ -n "$epoch" ]; then
        version_str="$epoch:$version-$release"
    fi

    # change release
    sed -i "/^Release.*$/s/${release_old}/${release}/" "$file"

    # update changelog
    local date_str=$(env LC_TIME=en_US.UTF-8 date +"%a %b %d %Y")
    local name=$(setting_get user name)
    local email=$(setting_get user email)
    local changelog="* $date_str $name <$email> - $version_str\n- $log\n"
    sed -i "/^%changelog/a $changelog" "$file"
}

usage_spec() {
    module_usage "spec" "Handle the spec file of rpm"
}

do_spec() {
    module_do "spec" "$@"
}

__spec_get_subcmd() {
    local cmd=""
    case "${1}" in
        "h"|"help")
            cmd="help"
            ;;
        "p"|"prep"|"prepare")
            cmd="prepare"
            ;;
        "up"|"update")
            cmd="update"
            ;;
        "d"|"date")
            cmd="date"
            ;;
        *)
            ;;
    esac
    echo "$cmd"
}

spec_usage_help() {
    module_usage_help spec
}

# spec_do_help subcmd
spec_do_help() {
    module_do_help spec "$1"
}

spec_usage_prepare() {
printf "prepare (p, prep): Prepare/init the spec file

usage:
    ${PROG} spec p SPEC
\n"
}

spec_do_prepare() {
    if [ $# -ne 1 ]; then
        spec_usage_prepare; exit
    fi

    local file_o=$1
    local file=${file_o}.cp
    cp -f "$file_o" "$file"

    # convert tab
    sed -i -e 's/\t/    /g' "$file"
    # delete comment
    sed -i '/^#.*/d' "$file"
    # delete Group Tags
    sed -i '/^Group.*/d' "$file"
    # clear changelog
    sed -i '/^%changelog/{p;:x;N;$!bx;d}' "$file"

    __spec_update "$file" "package init" y

    # align and merge tags
    local content=$(cat "$file" | spec_align_tag | spec_merge_tag "BuildRequires" | spec_merge_tag "Requires")
    echo "$content" > "$file"

    # insert enter (pre contains prep)
    sed -i -e '/^$/d'           \
        -e '/^%package.*/i\ '   \
        -e '/^%pre.*/i\ '       \
        -e '/^%post.*/i\ '      \
        -e '/^%build.*/i\ '     \
        -e '/^%install.*/i\ '   \
        -e '/^%check*/i\ '      \
        -e '/^%files.*/i\ '     \
        -e '/^%changelog.*/i\ ' "$file"

    # delete blank
    sed -i 's/\s*$//g' "$file"

    mv -b -f "$file" "$file_o"
}

spec_usage_update() {
printf "update (up): Update the spec file

usage:
    ${PROG} spec up SPEC \"changelog\" [resetflag]

params:
    changelog: the content of log, please use \\\n to wrap multiple lines
    resetflag: the flag of reset release, y or n. (Optional)

examples:
    ${PROG} spec up xx.spec \"Upgrade to x.y.z\" y
    ${PROG} spec up xx.spec \"Fix CVE-XXXX-YYYY\"
\n"
}

# spec_do_update spec changelog [resetflag]
spec_do_update() {
    if [ $# -lt 2 ]; then
        spec_usage_update; exit
    fi
    __spec_update "$1" "$2" "$3"
}

spec_usage_date() {
printf "date (d): Generate the date of changlog header

usage:
    ${PROG} spec d
\n"
}

spec_do_date() {
    local date_str=$(env LC_TIME=en_US.UTF-8 date +"%a %b %d %Y")
    local name=$(setting_get user name)
    local email=$(setting_get user email)
    if [[ -n "$name" && -n "$email" ]]; then
        echo "* $date_str $name <$email> -"
    else
        echo "$date_str"
    fi
}

spec_docheck() {
    check_command "rpmspec"
}
